/*
 * Copyright (C) 2012 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the 
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>

#include "usb.h"
#include <libusb-1.0/libusb.h>

static int libusb_initted = 0;
static libusb_context *ctx;

usb_handle *usb_open(ifc_match_func callback)
{
	int err;
	usb_handle *handle = NULL;
	libusb_device_handle *dhandle;

	if (!libusb_initted) {
		if (libusb_init(&ctx) < 0)
			return NULL;

		libusb_initted = 1;
	}

//	libusb_set_debug(ctx, 100);

	libusb_device **list;
	ssize_t len = libusb_get_device_list(ctx, &list);
//	printf("len %d, list %p\n", len, list);
	if (len < 0)
		return NULL;

	int i;
	for (i = 0; i < len; i++) {
		struct libusb_device_descriptor desc;

//		printf("list entry %p\n", list[i]);
		if (libusb_get_device_descriptor(list[i], &desc) < 0)
			continue;

		struct usb_ifc_info info;
		
		// XXX fill it in better, but for now the callback only wants vid/pid
		memset(&info, 0, sizeof(info));
		info.dev_vendor = desc.idVendor;
		info.dev_product = desc.idProduct;

//		printf("calling callback: 0x%x, 0x%x\n", desc.idVendor, desc.idProduct);
		if (callback(&info) >= 0) {
//			printf("found device\n");

			libusb_device *dev = libusb_ref_device(list[i]);

			if (libusb_open(dev, &dhandle) < 0) {
				goto err1;
			}

			err = libusb_set_configuration(dhandle, 1);
//			printf("libusb_set_config returns %d\n", err);

			err = libusb_claim_interface(dhandle, 0);
//			printf("libusb_claim returns %d\n", err);
			if (err < 0) {
				printf("failed to claim interface\n");
				libusb_close(dhandle);
				goto err1;
			}
			
//			printf("got device, opened\n");

			handle = (void *)dhandle;

			break;
		}
	}

	libusb_free_device_list(list, 1);

	return handle;

err1:
	libusb_free_device_list(list, 0);
err:
	return NULL;
}

int usb_close(usb_handle *h)
{
	usb_close(h);
	return 0;
}

int usb_read(usb_handle *h, void *_data, int len)
{
//	printf("read: %p, data %p, len %d\n", h, _data, len);

	int actual = len;
	int err = libusb_bulk_transfer(h, 0x81, _data, len, &actual, 5000);
//	printf("read returns %d, actual %d\n", err, actual);
	if (err < 0)
		return -1;

	return actual;
}

int usb_write(usb_handle *h, const void *_data, int len)
{
//	printf("write: %p, data %p, len %d\n", h, _data, len);

	int actual = len;
	int err = libusb_bulk_transfer(h, 0x01, _data, len, &actual, 5000);
//	printf("write returns %d, actual %d\n", err, actual);
	if (err < 0)
		return -1;

	return actual;
}

